import 'react-slidedown/lib/slidedown.css' // 导入 react-slidedown 的样式
import './style.css' // 导入自定义样式

import { Button, Progress, message } from 'antd' // 从 antd 库中导入 Button、Progress 和 message 组件

import $axios from '@/sever' // 导入自定义的 axios 实例
import { SlideDown } from 'react-slidedown' // 导入 SlideDown 组件，用于折叠效果
import SparkMD5 from 'spark-md5' // 导入 SparkMD5 库，用于计算文件的 MD5 值

const BaseUrl = '' // API 基础 URL

// 初始状态，包括校验和上传的进度
const initialState = { checkPercent: 0, uploadPercent: 0 }

// Reducer 函数，用于管理组件状态
function reducer(state, action) {
  switch (action.type) {
    case 'check':
      // 更新校验进度
      initialState.checkPercent = action.checkPercent
      return { ...initialState }
    case 'upload':
      // 更新上传进度
      initialState.uploadPercent = action.uploadPercent
      return { ...initialState }
    case 'init':
      // 初始化进度
      initialState.checkPercent = 0
      initialState.uploadPercent = 0
      return { ...initialState }
    default:
      // 返回当前状态
      return {
        checkPercent: state.checkPercent,
        uploadPercent: state.uploadPercent
      }
  }
}

const Upload = () => {
  const [state, dispatch] = useReducer(reducer, initialState) // 使用 useReducer 钩子来管理状态
  const inputRef = useRef(null) // 使用 useRef 钩子来引用文件输入元素
  const chunkSize = 5 * 1024 * 1024 // 每个切片的大小为 5MB
  let checkCurrentChunk = 0 // 当前校验的切片序号
  let uploadCurrentChunk = 0 // 当前上传的切片序号

  /**
   * 将文件转换为 MD5 并进行切片处理
   * @param {File} file 要处理的文件
   * @returns {Promise<string>} 文件的 MD5 哈希值
   */
  const md5File = (file) => {
    return new Promise((resolve) => {
      const blobSlice =
          File.prototype.slice || // 文件切片方法
          File.prototype.mozSlice ||
          File.prototype.webkitSlice,
        chunkSize = file?.size / 100, // 将文件分成 100 个切片
        spark = new SparkMD5.ArrayBuffer(), // 创建 SparkMD5 实例
        fileReader = new FileReader() // 创建 FileReader 实例

      fileReader.onload = function (e) {
        spark.append(e.target.result) // 追加文件切片内容
        checkCurrentChunk += 1 // 当前校验切片数加一

        if (checkCurrentChunk < 100) {
          loadNext() // 读取下一个切片
        } else {
          const result = spark.end() // 计算 MD5 值
          resolve(result) // 返回 MD5 值
        }
      }

      fileReader.onerror = function () {
        message.error('文件读取错误') // 读取文件出错时显示错误消息
      }

      const loadNext = () => {
        const start = checkCurrentChunk * chunkSize,
          end = start + chunkSize >= file.size ? file.size : start + chunkSize

        // 读取文件切片
        fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
        // 更新校验进度条
        dispatch({ type: 'check', checkPercent: checkCurrentChunk + 1 })
      }

      loadNext() // 开始读取第一个切片
    })
  }

  /**
   * 校验文件的 MD5 值是否存在
   * @param {string} fileName 文件名
   * @param {string} fileMd5Value 文件的 MD5 值
   * @returns {Promise} 校验结果
   */
  const checkFileMD5 = (fileName, fileMd5Value) => {
    const url =
      BaseUrl +
      '/check/file?fileName=' +
      fileName +
      '&fileMd5Value=' +
      fileMd5Value
    return $axios.get(url) // 发送 GET 请求校验文件的 MD5 值
  }

  /**
   * 上传文件的切片
   * @param {Object} params 包含切片信息的对象
   * @returns {Promise} 上传结果
   */
  function upload({ i, file, fileMd5Value, chunks }) {
    uploadCurrentChunk = 0 // 初始化当前上传切片序号
    const end =
      (i + 1) * chunkSize >= file.size ? file.size : (i + 1) * chunkSize
    const form = new FormData()
    form.append('data', file.slice(i * chunkSize, end)) // 切片文件
    form.append('total', chunks) // 总切片数
    form.append('index', i) // 当前切片序号
    form.append('fileMd5Value', fileMd5Value) // 文件的 MD5 值

    return $axios({
      method: 'post',
      url: BaseUrl + '/upload',
      data: form
    }).then((data) => {
      if (data.stat) {
        uploadCurrentChunk += 1 // 当前上传切片数加一
        const uploadPercent = Math.ceil((uploadCurrentChunk / chunks) * 100) // 计算上传进度百分比
        dispatch({ type: 'upload', uploadPercent }) // 更新上传进度条
      }
    })
  }

  /**
   * 检查并上传文件的切片
   * @param {File} file 要上传的文件
   * @param {string} fileMd5Value 文件的 MD5 值
   * @param {Array} chunkList 已存在的切片列表
   * @returns {Promise} 上传结果
   */
  async function checkAndUploadChunk(file, fileMd5Value, chunkList) {
    const chunks = Math.ceil(file.size / chunkSize) // 计算总切片数
    const requestList = []
    for (let i = 0; i < chunks; i++) {
      const exit = chunkList.indexOf(i + '') > -1 // 检查切片是否已经存在
      if (!exit) {
        requestList.push(upload({ i, file, fileMd5Value, chunks })) // 如果不存在，则上传
      }
    }

    if (requestList?.length) {
      await Promise.all(requestList) // 并发上传所有切片
    }
  }

  /**
   * 文件选择后的处理逻辑
   * @param {File} file 选择的文件
   */
  const responseChange = async (file) => {
    const fileMd5Value = await md5File(file) // 计算文件的 MD5 值
    const data = await checkFileMD5(file.name, fileMd5Value) // 校验文件的 MD5 值
    if (data?.file) {
      message.success('文件已秒传') // 如果文件已存在，显示秒传成功消息
      return
    }
    await checkAndUploadChunk(file, fileMd5Value, data.chunkList) // 检查并上传文件切片
    notifyServer(file, fileMd5Value) // 通知服务器文件切片上传完成
  }

  /**
   * 通知服务器文件切片上传完成，进行合并
   * @param {File} file 上传的文件
   * @param {string} fileMd5Value 文件的 MD5 值
   */
  function notifyServer(file, fileMd5Value) {
    const url =
      BaseUrl +
      '/merge?md5=' +
      fileMd5Value +
      '&fileName=' +
      file.name +
      '&size=' +
      file.size
    $axios.get(url).then((data) => {
      if (data.stat) {
        message.success('上传成功') // 显示上传成功消息
      } else {
        message.error('上传失败') // 显示上传失败消息
      }
    })
  }

  useEffect(() => {
    const changeFile = ({ target }) => {
      dispatch({ type: 'init' }) // 初始化进度
      const file = target.files[0]
      responseChange(file) // 处理文件选择
    }

    document.addEventListener('change', changeFile) // 添加文件选择事件监听

    return () => {
      document.removeEventListener('change', changeFile) // 移除文件选择事件监听
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return (
    <div className="wrap">
      <div className="upload">
        <span>点击上传文件：</span>
        <input ref={inputRef} type="file" id="file" /> {/* 文件输入框 */}
        <Button type="primary" onClick={() => inputRef.current.click()}>
          上传
        </Button>
      </div>
      {state.checkPercent > 0 && ( // 显示校验进度条
        <SlideDown className={'my-dropdown-slidedown'}>
          <div className="uploading">
            <div>
              校验文件进度：
              <Progress style={{ width: 200 }} percent={state.checkPercent} />
            </div>
          </div>
        </SlideDown>
      )}
      {state.uploadPercent > 0 && ( // 显示上传进度条
        <SlideDown className={'my-dropdown-slidedown'}>
          <div className="uploading">
            上传文件进度：
            <Progress type="circle" percent={state.uploadPercent} />
          </div>
        </SlideDown>
      )}
    </div>
  )
}

export default Upload // 导出 Upload 组件
